package com.mumu.muPictureBackend.service.impl;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mumu.muPictureBackend.exception.BusinessException;
import com.mumu.muPictureBackend.exception.ErrorCode;
import com.mumu.muPictureBackend.exception.ThrowUtils;
import com.mumu.muPictureBackend.mapper.SpaceMapper;
import com.mumu.muPictureBackend.model.dto.Space.CreateSpaceRequest;
import com.mumu.muPictureBackend.model.dto.Space.DeleteSpaceRequest;
import com.mumu.muPictureBackend.model.dto.Space.SpacePageRequest;
import com.mumu.muPictureBackend.model.dto.Space.UpdateSpaceRequest;
import com.mumu.muPictureBackend.model.entity.Space;
import com.mumu.muPictureBackend.model.entity.User;
import com.mumu.muPictureBackend.model.enums.SpaceEnum;
import com.mumu.muPictureBackend.service.SpaceService;
import com.mumu.muPictureBackend.service.UserService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class SpaceServiceImpl extends ServiceImpl<SpaceMapper, Space> implements SpaceService {

    @Resource
    UserService userService;

    /**
     * 创建个人空间
     *
     * @param createSpaceRequest
     * @param httpServletRequest
     * @return
     */
    @Resource
    private RedissonClient redissonClient;
    @Autowired
    private TransactionTemplate transactionTemplate;

    @Override
    public Long createSpace(CreateSpaceRequest createSpaceRequest, HttpServletRequest httpServletRequest) {
        //1. 校验参数
        // 判断是否登录 参数校验
        User loginUser = userService.getLoginUser(httpServletRequest);
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR, "登录后再来创建个人空间");
        ThrowUtils.throwIf(createSpaceRequest == null, ErrorCode.NOT_FOUND_ERROR);
        // 根据当前用户id来看看是否已经有空间了 如果有 那么就不要下去竞争锁了
        Long userId = loginUser.getId();
        //2. 创建空间 枷锁

        String lockKey = "space:createLock:" + userId;
        RLock lock = redissonClient.getLock(lockKey);
        boolean locked = false;
        try {

            //尝试加锁 最多等待一秒 拿到锁之后10秒自动释放锁
            locked = lock.tryLock(1, 30, TimeUnit.SECONDS);
            //如果抢到锁了就去创建空间 所以同一时间下只会有一个线程进去创建空间 其他线程只会在外面等着 一个一个来 解决并发问题(这里获取锁失败就直接下去抛出异常了 获取锁失败就说明已经有线程正在创建空间) 并且还是分布式锁在集群中也生效
            if (BooleanUtil.isTrue(locked)) {
                boolean exists = lambdaQuery().eq(Space::getUserId, userId).exists();
                //如果该用户创建过空间
                ThrowUtils.throwIf(exists, ErrorCode.OPERATION_ERROR, "一个人只能创建一个空间 不要重复");
                //这里使用纯@Transactional事务注解就会出现这个问题:事务提交之后这个方法里面的所有数据库操作才会提交到数据库 如果这里创建完空间释放锁之后不是立刻提交事务 也就是数据库中还没有该记录 如果这个时候有线程拿到锁进来看到没有创建空间就会去创建空间了
                //所以要事务提交后才释放锁
                //返回只是空间id Long类型 执行完这个代码的时候 事务已经提交到了数据库 或者已经回滚  事务提交后再释放锁
                Long createSpaceId = transactionTemplate.execute(status -> {
                    //如果该用户没有创建过空间->构建空间->创建空间
                    Integer spaceLevel = createSpaceRequest.getSpaceLevel();
                    String spaceName = createSpaceRequest.getSpaceName();
                    if (StrUtil.isBlank(spaceName)) {
                        spaceName = "个人空间";
                    }
                    //构建空间对象 然后存入到数据库中
                    Space space = Space.builder()
                            .spaceLevel(spaceLevel)
                            .spaceName(spaceName)
                            .userId(userId)
                            .build();
                    setSpaceLevel(space);
                    //对名字和级别进行校验
                    validSpace(space, true);
                    ThrowUtils.throwIf(!save(space), ErrorCode.OPERATION_ERROR);//这里给userId添加了唯一索引 唯一性更好

                    return space.getId();
                });
                log.info("用户[{}]事务已成功提交，准备释放锁", userId);
                return createSpaceId;

            }
        } catch (InterruptedException e) {
            // 1. 恢复中断状态 - 告诉JVM这个线程仍应被视为已中断
            Thread.currentThread().interrupt();

            // 2. 然后处理异常
            log.error("操作被中断", e);
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "操作被中断，请重试");
        } finally {
            if (locked)
                lock.unlock();
        }
        throw new BusinessException(ErrorCode.OPERATION_ERROR, "系统繁忙，创建空间请求过多，请稍后再试");
    }

    /**
     * 更新空间
     *
     * @param updateSpaceRequest
     * @param httpServletRequest
     * @return
     */
    @Override
    @Transactional
    public Boolean updateSpace(UpdateSpaceRequest updateSpaceRequest, HttpServletRequest httpServletRequest) {
        //1. 校验参数
        userService.isLoginAdmin(httpServletRequest);
        ThrowUtils.throwIf(updateSpaceRequest == null || updateSpaceRequest.getId() == null, ErrorCode.NOT_FOUND_ERROR);
        // 拿出老数据看看是否存在
        Space oldSpace = getById(updateSpaceRequest.getId());
        ThrowUtils.throwIf(oldSpace == null, ErrorCode.PARAMS_ERROR);
        //2. 更新数据
        Long id = updateSpaceRequest.getId();
        String name = updateSpaceRequest.getName();
        Integer level = updateSpaceRequest.getSpaceLevel();
        //3. 更新数据
        Space newSpace = Space.builder().id(id).build();
        boolean hasUpdate = false;
        // 如果name不为空并且name和原来的name不一样 那么更新一下name
        if (name != null && !oldSpace.getSpaceName().equals(name)) {
            newSpace.setSpaceName(name);
            hasUpdate = true;
        }
        // 如果等级不为空 那么还要更新等级
        if (level != null && !oldSpace.getSpaceLevel().equals(level)) {
            newSpace.setSpaceLevel(level);
            setSpaceLevel(newSpace);
            hasUpdate = true;
        }
        //更新数据库
        if (hasUpdate) {//如果更新过
            ThrowUtils.throwIf(!updateById(newSpace), ErrorCode.OPERATION_ERROR);
            return true;//到这里说明更新了并且更新成功了
        }
        return false;
    }

    private static void setSpaceLevel(Space space) {
        //这里等级直接利用枚举来获取 就不要那么多的if了
        SpaceEnum spaceLevel = SpaceEnum.getSpaceLevel(space.getSpaceLevel());
        ThrowUtils.throwIf(spaceLevel == null, ErrorCode.PARAMS_ERROR);
        space.setSpaceLevel(spaceLevel.getValue());
        space.setMaxCount(spaceLevel.getMaxCount());
        space.setMaxSize(spaceLevel.getMaxSize());
    }

    /**
     * 查看个人空间列表(管理员)
     *
     * @param spacePageRequest
     * @param httpServletRequest
     * @return
     */
    @Override
    public Page<Space> getSpaceList(SpacePageRequest spacePageRequest, HttpServletRequest httpServletRequest) {
        //1. 校验参数
        // 用户的私人空间列表只有管理员权限才能查看
        userService.isLoginAdmin(httpServletRequest);
        ThrowUtils.throwIf(spacePageRequest == null, ErrorCode.NOT_FOUND_ERROR);
        //2. 构建Page
        int pageSize = spacePageRequest.getPageSize();
        int current = spacePageRequest.getCurrent();
        return page(new Page<>(current, pageSize), getEq(spacePageRequest));
    }

    /**
     * 删除空间
     *
     * @param deleteSpaceRequest
     * @param httpServletRequest
     * @return
     */
    @Transactional
    @Override
    public Boolean deleteSpace(DeleteSpaceRequest deleteSpaceRequest, HttpServletRequest httpServletRequest) {
        //1. 校验参数
        userService.isLoginAdmin(httpServletRequest);
        ThrowUtils.throwIf(deleteSpaceRequest == null || deleteSpaceRequest.getId() == null, ErrorCode.NOT_FOUND_ERROR);
        //2. 删除
        ThrowUtils.throwIf(!removeById(deleteSpaceRequest.getId()), ErrorCode.OPERATION_ERROR);
        return true;
    }


    /**
     * 校验空间信息
     *
     * @param space 空间对象
     * @param add   是否为创建操作（true：创建，false：修改）
     */
    @Override
    public void validSpace(Space space, boolean add) {
        // 从对象中取值
        String spaceName = space.getSpaceName();
        Integer spaceLevel = space.getSpaceLevel();
        SpaceEnum spaceEnum = SpaceEnum.getSpaceLevel(spaceLevel);

        // 创建时校验
        if (add) {
            if (StrUtil.isBlank(spaceName)) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "空间名称不能为空");
            }
            if (spaceLevel == null) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "空间级别不能为空");
            }
        }

        // 修改数据时，空间名称进行校验
        if (StrUtil.isNotBlank(spaceName) && spaceName.length() > 30) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "空间名称过长");
        }

        if (spaceLevel != null && spaceEnum == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "空间级别不存在");
        }
    }


    /**
     * 构建出条件
     *
     * @param spacePageRequest
     * @return
     */
    private LambdaQueryWrapper<Space> getEq(SpacePageRequest spacePageRequest) {
        if (spacePageRequest == null) {
            return new LambdaQueryWrapper<>();
        }
        LambdaQueryWrapper<Space> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 空间名称：模糊查询（通常用于搜索）
        if (StrUtil.isNotBlank(spacePageRequest.getSpaceName())) {
            lambdaQueryWrapper.like(Space::getSpaceName, spacePageRequest.getSpaceName());
        }

        // 用户ID：精确匹配
        if (spacePageRequest.getUserId() != null) {
            lambdaQueryWrapper.eq(Space::getUserId, spacePageRequest.getUserId());
        }

        // 空间等级：精确匹配
        if (spacePageRequest.getSpaceLevel() != null) {
            lambdaQueryWrapper.eq(Space::getSpaceLevel, spacePageRequest.getSpaceLevel());
        }

        return lambdaQueryWrapper;
    }
}
